package Flap::Schema;
use Flap qw/-b Flap::Object -m -r -s -w/;
use Flap::Schema::Core::Relationship;
use Flap::Schema::Core::Row;
use constant COLUMN_OPTIONS => {
    -d => 1, #default (implement with perl, not DB, for validation)
    -e => 1, #enum
    -i => 1, #inflate
    -l => 1, #label
    -n => 1, #nullable
    -s => 1, #size
    -t => 1, #type
    -u => 1, #unsigned
    -v => 1, #validation
};

sub add_columns {
    die $_[0]->_dmsg_(1) if ref $_[0];
    my $sch = shift;
    my $co = $sch->COLUMN_OPTIONS;
    while(my $col = lc(shift)){
        my $ci = shift || die qq{No column info for "$sch.$col"};
        die $sch->_dmsg_(4, $col) if $sch->can($col);
        die qq{No type(-t) for "$_[0].$_[1]"} if !$ci->{-t};
        $co->{$_} || die qq{Invalid column option "$_" for "$_[0].$_[1]"} for keys %$ci;
        $sch->_add_column_($col, $ci);
    }
    $sch;
}

sub _add_column_ {
    my($sch, $col, $ci) = @_;
    $ci = {%$ci, -t => lc($ci->{-t})};
    $sch->storage->on_add_column($sch, $col, $ci);
    $ci->{-i} = [map{ $sch->_symbol_($_."_$ci->{-i}") } qw/inflate deflate/] if $ci->{-i};
    
    my $g = $sch->_global_;
    push @{$g->{columns} ||= []}, $col;
    $g->{column_info}{$col} = $ci;
    $sch->_define_method_($col, sub{
        @_ > 1 ? shift->set_column($col, @_) : shift->get_column($col)
    });
}

sub add_constraint {
    die $_[0]->_dmsg_(1) if ref $_[0];
    my $sch = shift;
    push @{$sch->_global_->{constraints} ||= []}, shift;
    $sch;
}

sub add_index {
    die $_[0]->_dmsg_(1) if ref $_[0];
    my $sch = shift;
    my $g = $sch->_global_;
    !$g->{column_info}{$_} && die $sch->_dmsg_(5, $_) for @{$_[0]};
    push @{$g->{indexes} ||= []}, {%{$_[1] || {}}, columns => $_[0]};
    $sch;
}

sub columns { shift->_global_->{columns} }

sub column_info {
    my $h = shift->_global_->{column_info};
    @_ ? $h->{$_[0]} : $h
}

sub constraints { shift->_global_->{constraints} }

sub _dmsg_ {
    my $self = shift;
    my $cd = shift || die;
    (caller(1))[3] =~ /(\w+)\z/;
    $cd eq 1 ? qq{Can't call class method "$1" via $self} :
    $cd eq 2 ? qq{Can't call instance method "$1" via $self} :
    $cd eq 3 ? qq{Can't define $1 via instance $self} :
    $cd eq 4 ? qq{Can't define reserved method "$_[0]"} :
    $cd eq 5 ? qq{No such column: "$_[0]" on }.$self->_class_ :
    $cd eq 6 ? qq{Column "$_[0]" has not loaded} :
    $cd eq 7 ? qq{No primary_key on }.$self->_class_ :
    $cd eq 8 ? qq{Can't omit join condition for $self->$_[0]} :
    die "$cd?";
}

sub find {
    die $_[0]->_dmsg_(1) if ref $_[0];
    my $sch = shift;
    my @id = ref $_[0] eq 'ARRAY' ? @{+shift} : (shift);
    my $opt = shift || {};
    my $pk = $sch->primary_key || die $sch->_dmsg_(7);
    scalar $sch->search({%$opt, where => [map{ ["$_ = ?", shift @id] } @$pk], rows => 1});
}

sub indexes { shift->_global_->{indexes} }

sub instantiate { shift->_new_({-org => shift}) }

sub new {
    die $_[0]->_dmsg_(1) if ref $_[0];
    my $row = shift->_new_({});
    my $txn = $row->{-txn} = {%{shift || {}}};
    my $cih = $row->_global_->{column_info};
    
    foreach(@{$row->columns}){
        next if exists $txn->{$_} || !exists((my $ci = $cih->{$_})->{-d});
        $row->set_column($_, ref $ci->{-d} eq 'CODE' ? $ci->{-d}->($row, $_) : $ci->{-d});
    }
    
    $row->_set_current_time_($row->SET_CURRENT_TIME_ON_NEW, $txn);
    
    my $lv = $row->LOCK_VERSION;
    $row->set_column($lv, 1) if $cih->{$lv} && !exists $txn->{$lv};
    
    $row;
}

sub primary_key {
    my $self = shift;
    my $g = $self->_global_;
    return $g->{primary_key} if !@_;
    die $self->_dmsg_(3) if ref $self;
    die 'No columns' if ref $_[0] ne 'ARRAY' || !@{$_[0]};
    !$g->{column_info}{$_} && die $self->_dmsg_(5, $_) for @{$_[0]};
    $g->{primary_key} = shift;
    $self;
}

sub _schema_name_ {
    my $class = shift->_class_;
    substr($class, rindex($class, '::') + 2);
}

sub _schema_names_ {
    my $self = shift;
    $self->project->_global_->{db}{$self->DB}{schema_names} ||= do{
        my $proj = $self->project;
        my $db = $self->DB;
        (my $pm = $proj->Schema->$db.'.pm') =~ s%::%/%g;
        $::INC{$pm} =~ /^(.+)\.pm\z/ || die $::INC{$pm};
        my $ls = $proj->OS->ls($1) || die "$!($1)";
        s/\.pm\z// for @$ls;
        $ls;
    };
}

sub search {
    die $_[0]->_dmsg_(1) if ref $_[0];
    $_[0]->storage->search(@_);
}

sub storage {
    my $self = shift;
    $self->project->_global_->{db}{$self->DB}{storage} ||= do{
        my $proj = $self->project;
        my $db = $self->DB;
        my $cfg = $proj->config->DB->$db;
        my $dsn = $cfg->dsn->[0][0];
        $dsn =~ /^dbi:(\w+):\w+/ || die qq{Can't parse dsn: "$dsn"};
        $proj->Schema->Storage->$1->new($db);
    };
}

sub table {
    my $self = shift;
    my $g = $self->_global_;
    return $g->{table} if !@_;
    die $self->_dmsg_(3) if ref $self;
    $g->{table} = shift;
    $self;
}

sub table_option {
    my $self = shift;
    my $g = $self->_global_;
    return $g->{table_option} ||= {} if !@_;
    die $self->_dmsg_(3) if ref $self;
    $g->{table_option} = shift;
    $self;
}

1;
